package com.huniu.analysis.dictionary.tree.manager;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

import org.apache.log4j.Logger;

import com.huniu.analysis.dictionary.tree.model.Node;
import com.huniu.analysis.dictionary.tree.model.TreeNode;
import com.huniu.analysis.dictionary.tree.model.ValuePair;
import com.huniu.utility.constant.GlobalConstants;

public class TreeNodeManagerImpl implements ITreeNodeManager {

	private static final Logger logger = Logger
			.getLogger(TreeNodeManagerImpl.class);
	private String txtFilePath;
	private String binFilePath;
	private boolean rebuild;

	public TreeNodeManagerImpl() {

	}

	public TreeNodeManagerImpl(String txtFilePath, String binFilePath,
			boolean rebuild) {
		this.txtFilePath = txtFilePath;
		this.binFilePath = binFilePath;
		this.rebuild = rebuild;
	}

	public void initial() {
		if (rebuild) {
			generateTreeNodes(txtFilePath);
			persistTreeNodes(binFilePath);
		} else {
			loadTreeNodes(binFilePath);
		}
	}

	public Node getRootNode() {
		TreeNode treeNode = null;
		Element rootElement = getRootElement();
		if (rootElement == null) {
			treeNode = new TreeNode();
			setRootElement(treeNode);
		} else {
			treeNode = (TreeNode) rootElement.getValue();
		}
		return treeNode;
	}

	public Set<String> findValuePairs(String statement) {
		Set<String> set = new HashSet<String>();
		Node rootNode = getRootNode();
		Node parentNode = null;
		for (char character : statement.toCharArray()) {
			ValuePair pair = new ValuePair(String.valueOf(character));
			Node foundNode = null;
			if (parentNode == null) {
				foundNode = findNode(rootNode, pair);
			} else {
				foundNode = findNode(parentNode, pair);
				if (foundNode == null) {
					foundNode = findNode(rootNode, pair);
				}
			}
			if (foundNode != null && foundNode.isEndNode()) {
				String words = getValuePairFromNodeChain(foundNode);
				set.add(words);
			}
			parentNode = foundNode;
		}
		return set;
	}

	public void generateTreeNodes(String filePath) {
		try {
			Charset chartset = Charset.forName(GlobalConstants.CHARSET);
			BufferedReader reader = new BufferedReader(new InputStreamReader(
					new FileInputStream(filePath),chartset));
			String line = null;
			Node rootNode = getRootNode();
			while ((line = reader.readLine()) != null) {
				String[] array = line.split("\\t");
				for (String item : array) {
					logger.info("Read dictionarie item : " + item);
					char[] charArray = item.toCharArray();
					addTreeNodes(rootNode, charArray);
				}
			}
			setRootElement(rootNode);
		} catch (FileNotFoundException e) {
			logger.error("Load file failure by path : " + filePath, e);
		} catch (IOException e) {
			logger.error("Read file failure ", e);
		}
	}

	public void persistTreeNodes(String filePath) {
		try {
			ObjectOutputStream out = new ObjectOutputStream(
					new FileOutputStream(filePath));
			Node rootNode = getRootNode();
			persistTreeNode(out, rootNode);
			out.close();
		} catch (FileNotFoundException e) {
			logger.error("File not found : " + filePath, e);
		} catch (IOException e) {
			logger.error("IO Exception ", e);
		}
	}

	private void persistTreeNode(ObjectOutputStream out, Node node)
			throws IOException {
		out.writeObject(node);
		Map<ValuePair, Node> children = node.getChildren();
		if (children != null && children.size() > 0) {
			out.writeInt(children.size());
			Set<Entry<ValuePair, Node>> set = children.entrySet();
			Iterator<Entry<ValuePair, Node>> iterator = set.iterator();
			while (iterator.hasNext()) {
				persistTreeNode(out, iterator.next().getValue());
			}
		} else {
			out.writeInt(0);
		}
	}

	public void loadTreeNodes(String filePath) {
		try {
			ObjectInputStream in = new ObjectInputStream(new FileInputStream(
					filePath));
			Node rootNode = (Node) in.readObject();
			loadTreeNode(in, rootNode);
			setRootElement(rootNode);
			in.close();
		} catch (FileNotFoundException e) {
			logger.error("File not found : " + filePath, e);
		} catch (IOException e) {
			logger.error("IO Exception ", e);
		} catch (ClassNotFoundException e) {
			logger.error("Class not found : " + Node.class, e);
		}
	}

	private void loadTreeNode(ObjectInputStream in, Node parent)
			throws IOException, ClassNotFoundException {
		int size = in.readInt();
		for (int i = 0; i < size; i++) {
			Node node = (Node) in.readObject();
			loadTreeNode(in, node);
			node.setParent(parent);
			parent.addChild(node.getPair(), node);
		}
	}

	private String getValuePairFromNodeChain(Node node) {
		StringBuffer sb = new StringBuffer();
		do {
			sb.append(node.getPair().getValue());
		} while (((node = node.getParent()) != null)
				&& (node.getPair() != null));
		return sb.reverse().toString();
	}

	private Node findNode(Node rootNode, ValuePair pair) {
		Map<ValuePair, Node> children = rootNode.getChildren();
		Node child = null;
		if (children != null) {
			child = children.get(pair);
		}
		return child;
	}

	private void addTreeNodes(Node rootNode, char[] array) {
		int length = array.length;
		Node addedNode = null;
		for (int i = 0; i < length; i++) {
			if (i == 0) {
				if (i + 1 == length) {
					logger.info("Add a Tree Node (End Node) to Root : "
							+ array[i]);
					addedNode = addTreeNode(rootNode, String.valueOf(array[i]),
							true);
				} else {
					logger.info("Add a Tree Node (Middle Node) to Root : "
							+ array[i]);
					addedNode = addTreeNode(rootNode, String.valueOf(array[i]),
							false);
				}
			} else {
				if (i + 1 == length) {
					logger
							.info("Add a Tree Node (End Node) to Dictionary Chain : "
									+ addedNode.getPair().getValue()
									+ "==>"
									+ array[i]);
					addedNode = addTreeNode(addedNode,
							String.valueOf(array[i]), true);
				} else {
					logger
							.info("Add a Tree Node (Middle Node) to Dictionary Chain : "
									+ addedNode.getPair().getValue()
									+ "==>"
									+ array[i]);
					addedNode = addTreeNode(addedNode,
							String.valueOf(array[i]), false);
				}
			}
		}
	}

	private Node addTreeNode(Node parent, String value, boolean isEndNode) {
		Map<ValuePair, Node> children = parent.getChildren();
		ValuePair pair = new ValuePair(value);
		TreeNode treeNode = null;
		if (children != null) {
			treeNode = (TreeNode) children.get(pair);
		}
		if (treeNode == null) {
			treeNode = new TreeNode(parent, pair, isEndNode);
			parent.addChild(pair, treeNode);
		}
		return treeNode;
	}

	private void setRootElement(Node rootNode) {
		CacheManager singletonManager = CacheManager.create();
		Cache cache = singletonManager
				.getCache(GlobalConstants.DICTIONARY_TREE_NODE_CACHE);
		Element rootElement = new Element(ROOT_TREE_NODE_ITEM, rootNode);
		cache.put(rootElement);
	}

	private Element getRootElement() {
		CacheManager singletonManager = CacheManager.create();
		Cache cache = singletonManager
				.getCache(GlobalConstants.DICTIONARY_TREE_NODE_CACHE);
		Element rootElement = cache.get(ROOT_TREE_NODE_ITEM);
		return rootElement;
	}
}
